﻿using System;
using System.ComponentModel;
using System.Drawing;
using System.Globalization;
using System.Windows.Forms;

namespace UILibrary
{
    internal class ShellTextBox : TextBox
    {
        internal bool asyncCall;

        private readonly CommandHistory _commandHistory = new CommandHistory();
        private readonly Container _components = null;

        private string _prompt = "> ";
        private string _promptAtLevel1 = "> ";
        private string _promptAtLevel2 = ">> ";

        private int _promptLevel = 1;

        public string CurrentLine
        {
            get { return GetCurrentLine().Substring(_prompt.Length); }
            set { ReplaceTextAtPrompt(value); }
        }

        public string InitText
        {
            set { SetInitText(value); }
        }

        public string Prompt
        {
            get { return _prompt; }
            set
            {
                if (PromptLevel == 2)
                    _promptAtLevel2 = value;
                else
                    _promptAtLevel1 = value;

                SetPromptText(value);
            }
        }

        public string PromptAtLevel1
        {
            get { return _promptAtLevel1; }
            set
            {
                _promptAtLevel1 = value;

                if (PromptLevel == 1)
                    Prompt = value;
            }
        }

        public string PromptAtLevel2
        {
            get { return _promptAtLevel2; }
            set
            {
                _promptAtLevel2 = value;

                if (PromptLevel == 2)
                    Prompt = value;
            }
        }

        public int PromptLevel
        {
            get { return _promptLevel; }
            set
            {
                _promptLevel = value;

                Prompt = _promptLevel == 2
                    ? PromptAtLevel2
                    : PromptAtLevel1;
            }
        }

        internal ShellTextBox()
        {
            InitializeComponent();
            PrintPrompt();
        }

        private void AddText(string text)
        {
            Text = Text + text;
            MoveCaretToEndOfText();
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing && _components != null)
                _components.Dispose();

            base.Dispose(disposing);
        }

        public string[] GetCommandHistory()
        {
            return _commandHistory.GetCommandHistory();
        }

        private int GetCurrentCaretColumnPosition()
        {
            var currentLine = GetCurrentLine();

            return SelectionStart - TextLength + currentLine.Length;
        }

        private string GetCurrentLine()
        {
            return Lines.Length > 0
                ? (string)Lines.GetValue(Lines.GetLength(0) - 1)
                : "";
        }

        private string GetTextAtPrompt()
        {
            try { return GetCurrentLine().Substring(_prompt.Length); }
            catch (ArgumentOutOfRangeException) { return ""; }
        }

        private void InitializeComponent()
        {
            SuspendLayout();
            AcceptsReturn = true;
            AcceptsTab = true;
            BackColor = Color.Black;
            Dock = DockStyle.Fill;
            ForeColor = Color.LawnGreen;
            MaxLength = 0;
            Multiline = true;
            ScrollBars = ScrollBars.Both;
            Size = new Size(400, 0xb0);
            KeyDown += ShellControlKeyDown;
            KeyPress += ShellTextBoxKeyPress;
            ResumeLayout(false);
        }

        private bool IsCaretAtCurrentLine()
        {
            return TextLength - SelectionStart <= GetCurrentLine().Length;
        }

        private bool IsCaretAtWritablePosition()
        {
            return IsCaretAtCurrentLine() && GetCurrentCaretColumnPosition() >= _prompt.Length;
        }

        private bool IsCaretJustBeforePrompt()
        {
            return IsCaretAtCurrentLine() && GetCurrentCaretColumnPosition() == _prompt.Length;
        }

        private static bool IsTerminatorKey(char keyChar)
        {
            return keyChar == '\r';
        }

        private static bool IsTerminatorKey(Keys key)
        {
            return key == Keys.Enter;
        }

        private void MoveCaretToEndOfText()
        {
            SelectionStart = TextLength;
            ScrollToCaret();
        }

        private void PrintLine()
        {
            AddText(Environment.NewLine);
        }

        internal void PrintPrompt()
        {
            var text = Text;

            if (text.Length != 0 && text[text.Length - 1] != '\n')
                PrintLine();

            AddText(_prompt);
        }

        private void ReplaceTextAtPrompt(string text)
        {
            var length = GetCurrentLine().Length - _prompt.Length;

            if (length == 0)
                AddText(text);
            else
            {
                Select(TextLength - length, length);
                SelectedText = text;
            }
        }

        private void SetInitText(string val)
        {
            Select(0, _prompt.Length);
            SelectedText = string.Format("{0}{1}{2}", val, Environment.NewLine, _prompt);
        }

        private void SetPromptText(string val)
        {
            GetCurrentLine();
            Select(0, _prompt.Length);
            SelectedText = val;
            _prompt = val;
        }

        private void ShellControlKeyDown(object sender, KeyEventArgs e)
        {
            if (!(IsCaretAtWritablePosition() || (e.Control || IsTerminatorKey(e.KeyCode))))
                MoveCaretToEndOfText();

            if (e.KeyCode == Keys.Left && IsCaretJustBeforePrompt())
                e.Handled = true;

            else switch (e.KeyCode)
                {
                    case Keys.Down:
                        if (_commandHistory.DoesNextCommandExist())
                            ReplaceTextAtPrompt(_commandHistory.GetNextCommand());
                        e.Handled = true;
                        break;

                    case Keys.Up:
                        if (_commandHistory.DoesPreviousCommandExist())
                            ReplaceTextAtPrompt(_commandHistory.GetPreviousCommand());
                        e.Handled = true;
                        break;

                    case Keys.Right:
                        var textAtPrompt = GetTextAtPrompt();
                        var lastCommand = _commandHistory.LastCommand;

                        if (lastCommand != null &&
                            (textAtPrompt.Length == 0 ||
                            lastCommand.StartsWith(textAtPrompt)) &&
                            lastCommand.Length > textAtPrompt.Length)
                            AddText(lastCommand[textAtPrompt.Length].ToString(CultureInfo.InvariantCulture));
                        break;

                    case Keys.Tab:
                        e.Handled = true;
                        break;
                }
        }

        private void ShellTextBoxKeyPress(object sender, KeyPressEventArgs e)
        {
            if (e.KeyChar == '\b' && IsCaretJustBeforePrompt())
                e.Handled = true;
            else
            {
                if (IsTerminatorKey(e.KeyChar))
                {
                    e.Handled = true;
                    var textAtPrompt = GetTextAtPrompt();

                    if (textAtPrompt.Length != 0 || PromptLevel != 1)
                    {
                        PrintLine();
                        ((ShellControl)Parent).FireCommandEntered(textAtPrompt);
                        _commandHistory.Add(textAtPrompt);
                    }

                    if (!(asyncCall && PromptLevel == 1))
                        PrintPrompt();
                }

                if (e.KeyChar == '\t')
                    e.Handled = true;
            }
        }

        protected override void WndProc(ref Message m)
        {
            switch (m.Msg)
            {
                case 0x0300:
                case 0x0302:
                case 0x000C:
                    if (!IsCaretAtWritablePosition())
                        MoveCaretToEndOfText();
                    break;

                case 0x0303:
                    return;
            }

            base.WndProc(ref m);
        }

        public void WriteText(string text)
        {
            AddText(text);
        }
    }
}

